﻿using AutoGenTool.Helpers;
using LiteDB;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace AutoGenTool.Core
{
    public class Processor
    {
        #region props

        public string AttributeName { get; private set; }
        public AutoGenConfig AutoGenConfig { get; private set; }

        #endregion

        #region fields

        private string[] _ignorePaths;
        private string _genCodePath;

        #endregion

        #region ctors

        public Processor(string attributeName, AutoGenConfig config)
        {
            AttributeName = attributeName;
            AutoGenConfig = config;
            CheckConfig(config);
            CreateIgnorePath();
        }

        #endregion

        #region methods
        private void CheckConfig(AutoGenConfig config)
        {
            var projectRoot = config.ProjectRoot;
            if (Directory.Exists(projectRoot) == false) throw new Exception($"process.CheckConfig: 无法找到 {projectRoot} 文件夹");
        }

        private void CreateIgnorePath()
        {
            var projectRoot = AutoGenConfig.ProjectRoot;
            var ignoreList = AutoGenConfig.IgnoreDir;
            var pathList = new List<string>();
            foreach (var item in ignoreList)
            {
                var ignoreItem = Helper.GetClearCombine(projectRoot, item);
                pathList.Add(ignoreItem);
            }

            {
                var genCodeDir = AutoGenConfig.GenCodeDir;
                var genCodeItem = Helper.GetClearCombine(projectRoot, genCodeDir);
                pathList.Add(genCodeItem);
                _genCodePath = genCodeItem;
            }

            _ignorePaths = pathList.ToArray();
        }

        public virtual void GenerateClass(ClassInfoContext context)
        {

        }

        public virtual void GenerateMethod(MethodInfoContext context)
        {

        }

        public void Process()
        {
            using var cancellationTokenSource = new CancellationTokenSource();
            Process(cancellationTokenSource.Token);
        }

        public void Process(CancellationToken cancellationToken)
        {
            var dbPath = Helper.GetClearCombine(AutoGenConfig.ProjectRoot, AutoGenConfig.CacheLocation);
            using var db = Helper.CreateOrGetDb(dbPath);
            var projectRoot = AutoGenConfig.ProjectRoot;

            Walkthought(projectRoot, cancellationToken, db);

        }

        ///<summary>创建生成的代码。</summary>
        protected string CreateGenCode(string genCodeFileName, string code)
        {
            if (string.IsNullOrWhiteSpace(genCodeFileName))
            {
                throw new Exception("Processor.CreateGenCode: 新添加的代码文件名称不能为空");
            }

            if (genCodeFileName.EndsWith(".cs") == false)
                genCodeFileName = $"{genCodeFileName}.gen.cs";

            var genCodePath = Helper.GetClearCombine(_genCodePath, genCodeFileName);

            var parentDir = Path.GetDirectoryName(genCodePath);

            FileSystemHelper.CreateFolder(parentDir);

            File.WriteAllText(genCodePath, code);

            return genCodePath;
        }

        private void Walkthought(string path, CancellationToken cancellationToken, ILiteDatabase db)
        {
            if (cancellationToken.IsCancellationRequested) return;
            if (IsIgnorePath(path)) return;

            var files = Directory.GetFiles(path, "*.cs", SearchOption.TopDirectoryOnly);

            foreach (var file in files)
            {
                if (cancellationToken.IsCancellationRequested) return;
                UpdateCsharpFile(file, cancellationToken, db);
            }

            var subdirs = Directory.GetDirectories(path);

            foreach (var subdir in subdirs)
            {
                if (cancellationToken.IsCancellationRequested) return;
                Walkthought(subdir, cancellationToken, db);
            }
        }

        private void UpdateCsharpFile(string path, CancellationToken cancellationToken, ILiteDatabase db)
        {
            var id = path;
            var newSha256 = Helper.GetSha256(path);
            var coll = db.GetCollection<DbFileRecord>();
            var oldRecord = coll.FindById(id);
            if (oldRecord == null || string.Equals(oldRecord.Sha256, newSha256) == false || AutoGenConfig.UseCache == false)
            {
                ProcessCsharpFile(path, cancellationToken);

                var newRecord = new DbFileRecord(id, newSha256);
                coll.Upsert(newRecord);
            }
            else
            {
                // DO NOTHING
            }
        }

        private void ProcessCsharpFile(string path, CancellationToken cancellationToken)
        {
            var syntaxTree = CSharpSyntaxTree.ParseText(File.ReadAllText(path));
            var root = syntaxTree.GetRoot();
            var namespaceDeclareResult = root.DescendantNodesAndSelf().OfType<UsingDirectiveSyntax>();

            var namespaceList = namespaceDeclareResult.Select(i => i.Name.ToString()).ToArray();

            var currentNamespaceResult = root.DescendantNodesAndSelf().OfType<NamespaceDeclarationSyntax>();

            foreach (var currentNamespace in currentNamespaceResult)
            {
                var currentNamespaceName = currentNamespace.Name.ToString();
                var classResult = currentNamespace.DescendantNodesAndSelf().OfType<ClassDeclarationSyntax>();

                foreach (var classNode in classResult)
                {
                    var className = classNode.Identifier.ValueText;

                    if (HasAttribute(classNode))
                    {

                        GenerateClass(new ClassInfoContext(namespaceList, currentNamespaceName, className));
                    }

                    ProcessCsharpFileExtractClassMethods(namespaceList, currentNamespaceName, className, classNode);
                }

            }

        }

        private void ProcessCsharpFileExtractClassMethods(string[] namespaceList, string currentNamespace, string className, ClassDeclarationSyntax classResult)
        {
            var methodNodes = classResult.DescendantNodesAndSelf().OfType<MethodDeclarationSyntax>();

            foreach (var methodNode in methodNodes)
            {
                if (HasAttribute(methodNode))
                {
                    ProcessCsharpFileMethodDeclaration(namespaceList, currentNamespace, className, methodNode);
                }
            }

            var funcNodes = classResult.DescendantNodesAndSelf().OfType<LocalFunctionStatementSyntax>();

            foreach (var funcNode in funcNodes)
            {
                if (HasAttribute(funcNode))
                {
                    ProcessCsharpFileMethodDeclaration(namespaceList, currentNamespace, className, funcNode);
                }
            }
        }

        private void ProcessCsharpFileMethodDeclaration(string[] namespaceList, string currentNamespace, string className, MethodDeclarationSyntax methodNode)
        {
            var parameterList = methodNode.ParameterList.DescendantNodesAndSelf().OfType<ParameterSyntax>();
            var paraList = new List<ArgumemtInfo>();

            foreach (var item in parameterList)
            {
                var type = item.ChildNodes().First().ToString();
                var name = item.ChildTokens().Last().ToString();
                paraList.Add(new ArgumemtInfo(type, name));
            }

            var funcName = methodNode.Identifier.Text.ToString();

            var returnType = methodNode.ReturnType.ToString();
            var modifier = methodNode.Modifiers.ToString();

            GenerateMethod(new MethodInfoContext(namespaceList, currentNamespace, className, funcName, paraList.ToArray(), returnType, modifier));
        }

        private void ProcessCsharpFileMethodDeclaration(string[] namespaceList, string currentNamespace, string className, LocalFunctionStatementSyntax methodNode)
        {
            var parameterList = methodNode.ParameterList.DescendantNodesAndSelf().OfType<ParameterSyntax>();
            var paraList = new List<ArgumemtInfo>();

            foreach (var item in parameterList)
            {
                var type = item.ChildNodes().First().ToString();
                var name = item.ChildTokens().Last().ToString();
                paraList.Add(new ArgumemtInfo(type, name));
            }

            var funcName = methodNode.Identifier.Text.ToString();

            var returnType = methodNode.ReturnType.ToString();
            var modifier = methodNode.Modifiers.ToString();

            GenerateMethod(new MethodInfoContext(namespaceList, currentNamespace, className, funcName, paraList.ToArray(), returnType, modifier));
        }

        private bool HasAttribute(ClassDeclarationSyntax node)
        {
            var attributeList = node.AttributeLists;

            var tostring = attributeList.ToString();
            return tostring.Contains($"[{AttributeName}");

        }

        private bool HasAttribute(MethodDeclarationSyntax node)
        {
            var attributeList = node.AttributeLists;

            var tostring = attributeList.ToString();
            return tostring.Contains($"[{AttributeName}");

        }

        private bool HasAttribute(LocalFunctionStatementSyntax node)
        {
            var attributeList = node.AttributeLists;

            var tostring = attributeList.ToString();
            return tostring.Contains($"[{AttributeName}");

        }

        ///<summary>是否是忽略的路径。</summary>
        private bool IsIgnorePath(string path)
        {
            foreach (var prefix in _ignorePaths)
            {
                if (Helper.IsPathPrefixEqual(prefix, path))
                {
                    return true;
                }
            }
            return false;
        }

        #endregion


    }
}
